package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLOutput;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*写tcp服务器大概流程：
1.先有一个socket文件（用来传输数据的，对应的是硬件的网卡设备），就像是一个遥控器
2.写构造方法：此时要把IP和端口号传进去就是和客户端进行tcp连接 在连接的时候new一个socket文件
3.写start方法（就是服务器启动之后需要执行的主逻辑（这里包含循环去读取客户端的请求，计算响应，把响应发送回给对端）
4.main方法启动服务器*/

/*但是此时的代码还是有bug的，此时的服务器只能处理一个客户端，如果是多个客户端都来请求这个服务器，
* 此时服务器就只能先等一个客户端下线之后才能响应第二个客户端，因为此时请求来了之后进入while循环
* 第一个客户端还没有下线，下面的processConnection方法也有一个循环，不下线这个循环就不会结束，
* 循环不会结束方法就不会结束，此时上边的while循环也就不能进入下一词循环，也就不能再给别的客户端分配一个
* socket文件，所以是只能对一个客户端提供服务  解决方案：多线程~~  */

/*改进之后其实频繁的创建和删除线程是有开销的，所以使用线程池来优化代码*/

public class TcpEchoServer1 {
    /*这里需要有两个socket文件，一个是serverSocket（场外管和一个一个的客户端连接用的）
     一个是socket（场内管给每一个客户端服务的，包括接收请求的数据，包括发送回的响应数据）*/
    private ServerSocket serverSocket = null;

    public TcpEchoServer1(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        //创建一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        System.out.println("服务器启动！");
        while (true) {
            //客户端没有启动的时候accept是阻塞着的，当客户端运行后，accept方法继续往下执行
            //如果直接调用，该方法会影响下一次循环不能及时accept
            //每次来一个新客户端都创建一个线程即可！！！
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
            /*Thread t1 = new Thread(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            t1.start();*/
            //然后就把一个一个的任务请求提交给线程池让它去执行即可。
            //一个连接的所有请求都处理完后并不会销毁线程，这个线程只是回到线程池中下次直接使用
            //注意：这里的线程的创建是一个一个循环创建的，但是建立连接和计算响应是并发执行的
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
            /*通过这个方法来处理一个连接（1.读取请求 2.根据请求计算响应 3.把响应的结果发送回给客户端）
            * tcp是面向字节流的，所以在读数据的时候也是通过socket来读取数据的*/
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线！\n",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        //try（）这种写法，（ ）中允许写多个流，每个流之间是用 ；来进行分割
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            /*但是是以字节流读取数据的，从哪到哪算是一个请求呢，此时就做一个约定，每读到一个\n就是一个请求*/
            Scanner scanner = new Scanner(inputStream);//scanner是读字符流的
            PrintWriter printWriter = new PrintWriter(outputStream);
            //所以把输入和 输出的字节流都变成字符流
            while (true) {
                //1.读取请求
                if (!scanner.hasNext()) {
                    //读到字符流的结尾了  如果它有下一个请求就继续往下读取请求的数据
                    System.out.printf("[%s:%d] 客户端下线！\n",clientSocket.getInetAddress(),
                            clientSocket.getPort());
                    break;
                }
                //没读到结尾，直接使用scanner来读取一段字符串（就一直读取数据）
                //由于计算机完成之后的数据是一个字符串，所以在发送回给客户端时也变成字符串得了
                String request = scanner.next();
                //2.根据请求计算响应
                String response = process(request);
                printWriter.println(response);//把这个字符串打印
//                printWriter.flush();
                System.out.printf("[%s；%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            clientSocket.close();
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer1 tcpEchoServer1 = new TcpEchoServer1(909);
        tcpEchoServer1.start();
    }
}
